1 module hip.assets.tilemap;
2 public import hip.api.data.tilemap;
3 import hip.config.opts;
4 import hip.assets.image;
5 import hip.asset;
6 
7 class HipTilesetImpl : HipAsset, IHipTileset
8 {
9     uint _columns; uint columns() const =>_columns;
10 
11     ///Means where the tileset id starts
12     uint _firstGid; uint firstGid() const => _firstGid;
13     
14 
15     ///"image" in tiled
16 
17     string _texturePath; string texturePath() const => _texturePath;
18     ///"imageheight" in tiled
19     uint  _textureHeight; uint  textureHeight() const => _textureHeight;
20     ///"imagewidth" in tiled 
21     uint  _textureWidth; uint  textureWidth() const => _textureWidth;
22     IHipTexture _texture; IHipTexture texture()     => _texture;
23     int _margin; int margin() const => _margin;
24 
25     override string name() const => super.name;
26 
27     ///Only available when loaded via .tsx
28     string _path; string path() const => _path;
29     int _spacing; int spacing() const => _spacing;
30     uint _tileHeight; uint tileHeight() const => _tileHeight;
31     uint _tileWidth; uint tileWidth() const => _tileWidth;
32     Tile[] _tiles; Tile[] tiles() => _tiles;
33 
34     void setTexture(IHipTexture texture)
35     {
36         this._texture = texture;
37         this._textureWidth = texture.getWidth;
38         this._textureHeight = texture.getHeight;
39     }
40 
41     // static if(hasTSXSupport)
42     // {
43     //     import arsd.dom;
44 
45     //     static Tileset fromTSX(ubyte[] tsxData, string tsxPath, bool autoLoadTexture = true)
46     //     {
47     //         string xmlFile = cast(string)tsxData;
48     //         auto document = new XmlDocument(xmlFile);
49     //         auto tileset = document.querySelector("tileset");
50     //         return Tileset.fromXMLElement(tileset, tsxPath, autoLoadTexture);
51     //     }
52 
53         
54     //     static Tileset fromXMLElement(Element tileset, string tsxPath="", bool autoLoadTexture=true)
55     //     {
56     //         auto image   = tileset.querySelector("image");
57 
58     //         const uint tileCount = to!uint(tileset.getAttribute("tilecount"));
59     //         Tileset ret = new Tileset(tileCount);
60     //         ret.path = tsxPath;
61 
62     //         //Tileset
63     //         ret.name        =         tileset.getAttribute("name");
64     //         ret.tileWidth   = to!uint(tileset.getAttribute("tilewidth"));
65     //         ret.tileHeight  = to!uint(tileset.getAttribute("tileheight"));
66     //         ret.columns     = to!uint(tileset.getAttribute("columns"));
67 
68     //         //Image
69     //         ret.texturePath   =         image.getAttribute("source");
70     //         ret.textureWidth  = to!uint(image.getAttribute("width"));
71     //         ret.textureHeight = to!uint(image.getAttribute("height"));
72 
73     //         if(autoLoadTexture)
74     //             ret.loadTexture();
75             
76     //         Element[] tiles = tileset.querySelectorAll("tile");
77 
78     //         foreach(t; tiles)
79     //         {
80     //             Tile tile;
81     //             tile.id = to!ushort(t.getAttribute("id"));
82     //             Element anim = t.querySelector("animation");
83     //             if(anim !is null)
84     //             {
85     //                 Element[] frames = anim.querySelectorAll("frame");
86     //                 tile.animation = new TileAnimationFrame[frames.length];
87 
88     //                 foreach(f; frames)
89     //                 {
90     //                     TileAnimationFrame tFrame;
91     //                     tFrame.id       = to!ushort(f.getAttribute("tileid"));
92     //                     tFrame.duration =    to!int(f.getAttribute("duration"));
93     //                 }
94     //             }
95     //         }
96 
97     //         return ret;
98     //     }
99 
100 
101     //     static Tileset fromTSX(string tsxPath, bool autoLoadTexture = true)
102     //     {
103     //         void[] tsxData;
104     //         if(!HipFS.read(tsxPath, tsxData))
105     //         {
106     //             import hip.error.handler;
107     //             ErrorHandler.showWarningMessage("Could not load TSX ", tsxPath);
108     //             return null;
109     //         }
110     //         return fromTSX(cast(ubyte[])tsxData, tsxPath, autoLoadTexture);
111     //     }
112 
113     //     protected static TileLayer tileLayerFromElement(Element l)
114     //     {
115     //         import hip.util.string:split;
116     //         import hip.util.file:stripLineBreaks;
117     //         TileLayer layer = new TileLayer();
118     //         layer.type    = TileLayerType.TILE_LAYER;
119     //         layer.id      = to!ushort(l.getAttribute("id"));
120     //         layer.name    =           l.getAttribute("name");
121     //         layer.width   =   to!uint(l.getAttribute("width"));
122     //         layer.height  =   to!uint(l.getAttribute("height"));
123     //         string[] data = l.querySelector("data").innerText.stripLineBreaks.split(",");
124     //         layer.tiles.reserve(data.length);
125     //         for(int i = 0; i < data.length;i++)
126     //             layer.tiles~=to!ushort(data[i]);
127             
128     //         return layer;
129     //     }
130 
131     //     protected static TileLayer objectLayerFromElement(Element objgroup)
132     //     {
133     //         TileLayer layer = new TileLayer();
134     //         layer.type = TileLayerType.OBJECT_LAYER;
135     //         layer.id   = toDefault!(ushort)(objgroup.getAttribute("id"));
136     //         layer.name = objgroup.getAttribute("name");
137     //         Element[] objs = objgroup.querySelectorAll("object");
138     //         foreach(o; objs)
139     //         {
140     //             TileLayerObject obj;
141     //             obj.gid     = toDefault!(ushort)(o.getAttribute("gid"));
142     //             obj.height  =   toDefault!(uint)(o.getAttribute("height"));
143     //             obj.id      = toDefault!(ushort)(o.getAttribute("id"));
144     //             obj.name    =            (o.getAttribute("name"));
145     //             obj.rotation=    toDefault!(int)(o.getAttribute("rotation"));
146     //             obj.type    =            (o.getAttribute("type"));
147     //             obj.visible =   toDefault!(bool)(o.getAttribute("visible"));
148     //             obj.width   =   toDefault!(uint)(o.getAttribute("width"));
149     //             obj.x       =    toDefault!(int)(o.getAttribute("x"));
150     //             obj.y       =    toDefault!(int)(o.getAttribute("y"));
151     //             Element[] props = o.querySelectorAll("properties");
152     //             foreach(p; props)
153     //             {
154     //                 TileProperty tp;
155     //                 tp.name  = p.getAttribute("name");
156     //                 tp.type  = p.getAttribute("type");
157     //                 tp.value = p.getAttribute("value");
158     //                 obj.properties[tp.name] = tp;
159     //             }
160     //         }
161     //         return layer;
162     //     }
163     //     static Tilemap readTiledTMX(string tiledPath)
164     //     {
165     //         void[] tmxData;
166     //         if(!HipFS.read(tiledPath, tmxData))
167     //         {
168     //             import hip.error.handler;
169     //             ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath);
170     //             return null;
171     //         }
172     //         return readTiledTMX(cast(ubyte[])tmxData, tiledPath);
173     //     }
174 
175     //     static Tilemap readTiledTMX(ubyte[] tiledData, string tiledPath, bool autoLoadTexture = true)
176     //     {
177     //         Tilemap ret = new Tilemap();
178     //         string xmlFile = cast(string)tiledData;
179     //         auto document = new XmlDocument(xmlFile);
180     //         auto map = document.querySelector("map");
181     //         ret.path = tiledPath;
182 
183     //         ret.tiled_version =         map.getAttribute("tiledVersion");
184     //         ret.orientation   =         map.getAttribute("orientation");
185     //         ret.width         = to!uint(map.getAttribute("width"));
186     //         ret.height        = to!uint(map.getAttribute("height"));
187     //         ret.tileWidth     = to!uint(map.getAttribute("tilewidth"));
188     //         ret.tileHeight    = to!uint(map.getAttribute("tileheight"));
189     //         ret.isInfinite    = (to!uint(map.getAttribute("infinite")) == 1);
190     //         ret.renderorder   =         map.getAttribute("renderorder");
191 
192     //         auto tileset = document.querySelectorAll("tileset");
193 
194     //         foreach(t; tileset)
195     //         {
196     //             string tsxPath = t.getAttribute("source");
197     //             Tileset set;
198     //             if(tsxPath != null)
199     //                 set = Tileset.fromTSX(ret.getTSXPath(tsxPath), autoLoadTexture);
200     //             else
201     //             {
202     //                 set = Tileset.fromXMLElement(t, ret.getTSXPath("null"));
203     //                 //Using getTSXPath with any string, as it will be replaced later 
204     //                 //For the texture path
205     //             }
206     //             set.firstGid = to!uint(t.getAttribute("firstgid"));
207     //             ret.tilesets~=set;
208     //         }
209 
210     //         auto layers = document.querySelectorAll("map > layer");
211     //         foreach(l; layers)
212     //         {
213     //             TileLayer layer = Tilemap.tileLayerFromElement(l);
214     //             ret.layersArray~= layer;
215     //             ret.layers[layer.name] = layer;
216     //         }
217 
218     //         Element[] objGroups = document.querySelectorAll("map > objectgroup");
219     //         foreach(objGroup; objGroups)
220     //         {
221     //             TileLayer layer = Tilemap.objectLayerFromElement(objGroup);
222     //             ret.layers[layer.name] = layer;
223     //         }
224 
225     //         return ret;
226     //     }
227 
228 
229     // }
230     // else static if(Version.HipTSX)
231     // {
232     //     static assert(false, `Please call dub add arsd-official:dom for using TSX parser`);
233     // }
234 
235     static HipTilesetImpl read (string path, void delegate(HipTilesetImpl self) onSuccess, void delegate() onError, uint firstGid = 1)
236     {
237         import hip.util.path;
238         switch(path.extension)
239         {
240             case "xml":
241             case "tmx":
242                 assert(false, `Please call dub add arsd-official:dom for using TSX parser`);
243             case "tsj":
244             case "json":
245                 return HipTilesetImpl.readJSON(path, firstGid, onSuccess, onError);
246             default:
247                 assert(false, "Unrecognized extension for file "~path);
248         }
249     }
250 
251     import hip.data.json;
252     static HipTilesetImpl readFromMemory (string path, string data, void delegate(HipTilesetImpl) onSuccess, void delegate() onError, uint firstGid = 1)
253     {
254         import hip.util.path;
255         switch(path.extension)
256         {
257             case "xml":
258             case "tmx":
259                 assert(false, `Please call dub add arsd-official:dom for using TSX parser`);
260             case "tsj":
261             case "json":
262                 HipTilesetImpl ret = new HipTilesetImpl(0);
263                 ret._path = path;
264                 ret._firstGid = firstGid;
265                 ret.loadJSON(parseJSON(data), onSuccess, onError);
266                 return ret;
267             default:
268                 assert(false, "Unrecognized extension for file "~path);
269         }
270     }
271 
272     static HipTilesetImpl readJSON (string path, uint firstGid, void delegate(HipTilesetImpl self) onSuccess, void delegate() onError)
273     {
274         import hip.filesystem.hipfs;
275         import hip.console.log;
276 
277         HipTilesetImpl tileset = new HipTilesetImpl(0);
278         tileset._path = path;
279         tileset._firstGid = firstGid;
280 
281         HipFS.readText(path).addOnSuccess((in void[] data)
282         {
283             tileset.loadJSON(parseJSON(cast(string)data), onSuccess, onError);
284         }).addOnError((err)
285         {
286             loglnWarn("Could not read file at path ", path," ", err);
287         });
288         return tileset;
289     }
290 
291     public static HipTilesetImpl readJSON (string path, uint firstGid, JSONValue t, void delegate(HipTilesetImpl self) onSuccess, void delegate() onError)
292     {
293         HipTilesetImpl ret = new HipTilesetImpl(0);
294         ret._path = path;
295         ret._firstGid = firstGid;
296         ret.loadJSON(t, onSuccess, onError);
297         return ret;
298     }
299 
300     private void loadJSON (JSONValue t, void delegate(HipTilesetImpl self) onSuccess, void delegate() onError)
301     {
302         if(t.hasErrorOccurred)
303         {
304             import hip.error.handler;
305             ErrorHandler.showErrorMessage("JSON Parsing Error on Tilemap", t.toString);
306             return onError();
307         }
308         _tiles = new Tile[cast(uint)t["tilecount"].integer];
309         _texturePath   =             t["image"].str;
310         _textureHeight =   cast(uint)t["imageheight"].integer;
311         _textureWidth  =   cast(uint)t["imagewidth"].integer;
312         _columns       = cast(ushort)t["columns"].integer;
313         _margin        =    cast(int)t["margin"].integer;
314         _name          =             t["name"].str;
315         _spacing       =    cast(int)t["spacing"].integer;
316         _tileHeight    =   cast(uint)t["tileheight"].integer;
317         _tileWidth     =   cast(uint)t["tilewidth"].integer;
318 
319         if("tiles" in t)
320         {
321             foreach (currentTile; t["tiles"].array)
322             {
323                 Tile tile;
324                 tile.id = cast(ushort)currentTile["id"].integer;
325                 
326                 foreach(prop; currentTile["properties"].array)
327                 {
328                     TileProperty _p;
329 
330                     _p.name  = prop["name"].str;
331                     _p.type  = prop["type"].str;
332                     _p.value = prop["value"].toString;
333                     tile.properties[_p.name] = _p;
334                 }
335                 tiles[tile.id] = tile;
336             }
337         }
338         onSuccess(this);
339     }
340 
341 
342     import hip.util.data_structures;
343     static IHipTileset fromSpritesheet(Array2D_GC!IHipTextureRegion regions)
344     {
345         import hip.error.handler;
346         import hip.assets.texture;
347         ErrorHandler.assertExit(regions.getWidth > 0 && regions.getHeight > 0, "Invalid spritesheet");
348         HipTilesetImpl t = new HipTilesetImpl(regions.getWidth * regions.getHeight);
349         t._name = "Created from Spritesheet";
350         t._firstGid = 1;
351         t.setTexture(regions[0,0].getTexture);
352         t._tileWidth = regions[0,0].getWidth();
353         t._tileHeight = regions[0,0].getHeight();
354         int i = 0;
355         for(int y = 0; y < regions.getHeight; y++)
356             for(int x = 0; x < regions.getWidth; x++)
357             {
358                 Tile* tile = &t.tiles[i++];
359                 tile.id = cast(ushort)i;
360                 tile.region = regions[x, y]; //TODO: May use clone one day if direct assign doesn't fit
361                 // t.region = (cast(HipTextureRegion)regions[x, y]).clone;
362             }
363 
364         return t;
365     }
366     import hip.assets.textureatlas;
367     /**
368     *   Untested. D's Associative Arrays aren't deterministic, this is subject to bug.
369     */
370     static IHipTileset fromAtlas(HipTextureAtlas atlas)
371     {
372         HipTilesetImpl t = new HipTilesetImpl(cast(uint)atlas.frames.length);
373         t._firstGid = 1;
374         t._name = "Tileset from Atlas: "~atlas.name;
375         t.setTexture(atlas.texture);
376         int i = 0;
377         foreach(atlasFrame; atlas)
378         {
379             Tile* tile = &t.tiles[i++];
380             if(!t.tileWidth)
381             {
382                 t._tileWidth = atlasFrame.region.getWidth;
383                 t._tileHeight = atlasFrame.region.getHeight;
384             }
385             //TODO: May use clone one day if direct assign doesn't fit.
386             tile.region = atlasFrame.region;
387             tile.id = cast(ushort)i;
388         }
389         return t;
390     }
391 
392     this(uint tileCount)
393     {
394         super("HipTileset");
395         _tiles = new Tile[tileCount];
396         _typeID = assetTypeID!HipTilesetImpl;
397     }
398     IImage textureImage;
399 
400     IImage loadImage(void delegate(IImage self) onSuccess, void delegate() onFailure)
401     {
402         import hip.error.handler;
403         import hip.filesystem.hipfs;
404         import hip.util.path;
405         if(textureImage is null)
406         {
407             ErrorHandler.assertExit(texturePath != "", "No texture path for loading tilemap texture");
408             string imagePath = replaceFileName(path, texturePath);
409             HipFS.read(imagePath).addOnSuccess((in ubyte[] imgData)
410             {
411                 textureImage = new Image(imagePath, cast(ubyte[])imgData, onSuccess, onFailure);
412             }).addOnError((string err)
413             {
414                 ErrorHandler.showErrorMessage("Error loading image required by Tileset", imagePath);
415                 onFailure();
416             });
417         }
418         return textureImage;
419     }
420 
421     bool loadTexture()
422     {
423         import hip.error.handler;
424         import hip.assets.texture;
425         if(textureImage is null)
426         {
427             loadImage((_){loadTexture();}, (){});
428             return false;
429         }
430         _texture = new HipTexture(textureImage);
431         int i = 0;
432         for(int y = margin; y < textureHeight; y+= (tileHeight+spacing))
433             for(int x = margin, currCol = 0 ; currCol < columns; currCol++, x+= (tileWidth+spacing))
434             {
435                 Tile* t = &tiles[i];
436                 t.region = new HipTextureRegion(texture, x, y, x+tileWidth, y+tileHeight);
437                 i++;
438             }
439 
440         return texture !is null && texture.hasSuccessfullyLoaded();
441     }
442     
443     override void onFinishLoading(){}
444     override void onDispose(){}
445     bool isReady(){return _texture !is null;}
446 }
447 
448 
449 class HipTilemap : HipAsset, IHipTilemap
450 {
451 
452     int _x, _y;
453     HipColor _color = HipColor.white;
454     float _scaleX = 1.0, _scaleY = 1.0;
455     float _rotation = 0;
456 
457     string _path;
458     uint _width, _height;
459     bool _isInfinite;
460     HipTileLayer[string] _layers;
461     string _orientation;
462     string _renderOrder;
463     string _tiledVersion;
464     uint _tileWidth, _tileHeight;
465 
466     this(uint width = 0, uint height = 0, uint tileWidth = 0, uint tileHeight = 0)
467     {
468         super("HipTilemap");
469         _typeID = assetTypeID!HipTilemap;
470         this._width = width;
471         this._height = height;
472         this._tileWidth = tileWidth;
473         this._tileHeight = tileHeight;
474     }
475 
476     ref int x() => _x;
477     ref int y() => _y;
478     ref HipColor color() => _color;
479     ref float scaleX() => _scaleX;
480     ref float scaleY() => _scaleY;
481     float scale() => _scaleX;
482     float scale(float sc) => _scaleX = _scaleY = sc;
483     ref float rotation() => _rotation;
484 
485     ///Used for rendering order
486     string path() const => _path;
487     uint width() const => _width;
488     uint height() const => _height;
489     bool isInfinite() const => _isInfinite;
490     ref HipTileLayer[string] layers() => _layers;
491     string orientation() const => _orientation;
492     string renderorder() const => _renderOrder;
493     string tiled_version() const => _tiledVersion;
494     uint tileHeight() const => _tileHeight;
495     uint tileWidth() const => _tileWidth;
496 
497     void setTileSize(uint tileWidth, uint tileHeight)
498     {
499         _tileWidth = tileWidth;
500         _tileHeight = tileHeight;
501     }
502 
503     void addTileset(IHipTileset tileset){tilesets~= cast(HipTilesetImpl)tileset;}
504 
505     protected HipTileLayer[] layersArray;
506     HipTilesetImpl[] tilesets;
507     this()
508     {
509         super("HipTilemap");
510         _typeID = assetTypeID!HipTilemap;
511     }
512 
513     IHipTileset getTilesetForID(ushort id)
514     {
515         if(tilesets.length == 0)
516             return null;
517         for(int i = 0; i < cast(int)tilesets.length-1; i++)
518         {
519             if(id >= tilesets[i].firstGid && id < tilesets[i+1].firstGid)
520                 return tilesets[i];
521         }
522         return tilesets[$-1];
523     }
524 
525 
526     string getTSXPath(string tsxName)
527     {
528         import hip.util.path : replaceFileName;
529         return replaceFileName(path, tsxName);
530     }
531 
532     static HipTilemap readTiledJSON (string mapPath, ubyte[] tiledData, void delegate(HipTilemap) onSuccess, void delegate() onError)
533     {
534         import hip.data.json;
535         HipTilemap ret = new HipTilemap();
536         ret._path = mapPath;
537         JSONValue json = parseJSON(cast(string)(tiledData));
538         ret._height     =    cast(uint)json["height"].integer;
539         ret._isInfinite =              json["infinite"].boolean;
540         ret._width      =    cast(uint)json["width"].integer;
541         ret._orientation=              json["orientation"].str;
542         ret._renderOrder=              json["renderorder"].str;
543         ret._tileHeight =    cast(uint)json["tileheight"].integer;
544         ret._tileWidth  =    cast(uint)json["tilewidth"].integer;
545 
546         foreach(l; json["layers"].array)
547         {
548             HipTileLayer layer = new HipTileLayer(ret);
549 
550             //Check first the layer type.
551             layer.type    =             l["type"].str;
552             layer.id      = cast(ushort)l["id"].integer;
553             layer.name    =             l["name"].str;
554             layer.opacity =             l["opacity"].integer;
555             layer.visible =             l["visible"].boolean;
556             layer.x       = cast(int)   l["x"].integer;
557             layer.y       = cast(int)   l["y"].integer;
558             layer.columns = cast(int)   l["width"].integer;
559             layer.rows    = cast(int)   l["height"].integer;
560             if(layer.type == TileLayerType.OBJECT_LAYER)
561             {
562                 foreach(o; l["objects"].array)
563                 {
564                     TileLayerObject obj;
565                     obj.gid     = cast(ushort)o["gid"].integer;
566                     obj.height  = cast(uint)  o["height"].integer;
567                     obj.id      = cast(ushort)o["id"].integer;
568                     obj.name    =             o["name"].str;
569                     obj.rotation= cast(int)   o["rotation"].integer;
570                     obj.type    =             o["type"].str;
571                     obj.visible =             o["visible"].boolean;
572                     obj.width   = cast(uint)  o["width"].integer;
573                     obj.x       = cast(int)   o["x"].integer;
574                     obj.y       = cast(int)   o["y"].integer;
575 
576                     const(JSONValue)* v = ("properties" in o);
577                     if(v != null)
578                     {
579                         foreach(p; v.array) //Properties
580                         {
581                             TileProperty tp;
582                             tp.name  = p["name"].str;
583                             tp.type  = p["type"].str;
584                             tp.value = p["value"].toString;
585 
586                             obj.properties[tp.name] = tp;
587                         }
588                     }
589                 }
590             }
591             else if(layer.type == TileLayerType.TILE_LAYER)
592             {
593                 auto layerData = l["data"].array;
594                 layer.height  = cast(uint)  l["height"].integer;
595                 layer.width   = cast(uint)  l["width"].integer;
596                 layer.tiles.reserve(layerData.length);
597                 foreach(d; layerData)
598                     layer.tiles~= cast(ushort)d.integer;
599             }
600 
601             const(JSONValue)* layerProp = ("properties" in l);
602             if(layerProp != null)
603             {
604                 foreach(p; layerProp.array)
605                 {
606                     TileProperty tp;
607                     tp.name  = p["name"].str;
608                     tp.type  = p["type"].str;
609                     tp.value = p["value"].toString;
610                     layer.properties[tp.name] = tp;
611                 }
612             }
613             ret.layersArray~=layer;
614             ret._layers[layer.name] = layer;
615         }
616 
617         size_t maxTilesets = json["tilesets"].array.length;
618         uint loadedCount = 0;
619         auto onTilesetLoad = delegate(HipTilesetImpl tileset)
620         {
621             if(++loadedCount == maxTilesets)
622                 onSuccess(ret);
623         };
624 
625 
626         foreach(t; json["tilesets"].array)
627         {
628             const(JSONValue)* source = ("source" in t);
629             uint firstGid = cast(ushort)t["firstgid"].integer;
630             HipTilesetImpl tileset;
631 
632             if(source !is null)
633             {
634                 import hip.util.path;
635                 import hip.console.log;
636                 loglnWarn("Reading from source ");
637                 tileset = HipTilesetImpl.read(joinPath(dirName(mapPath), source.str), onTilesetLoad, onError, firstGid);
638             }
639             else
640                 tileset = HipTilesetImpl.readJSON("null", firstGid, t, onTilesetLoad, onError);
641             ret.tilesets~= tileset;
642         }
643 
644         return ret;
645     }
646     static void readTiledJSON (string tiledPath, void delegate(HipTilemap) onSuccess, void delegate() onError)
647     {
648         import hip.filesystem.hipfs;
649         HipFS.read(tiledPath).addOnSuccess((in ubyte[] data)
650         {
651             HipTilemap.readTiledJSON(tiledPath, cast(ubyte[])data, onSuccess, onError);
652         }).addOnError((err)
653         {
654             import hip.error.handler;
655             ErrorHandler.showWarningMessage("Could not read Tiled TMX from path ", tiledPath);
656             onError();
657         });
658     }
659 
660 
661     ///Those arguments are actually synchronous on all platforms. This is to simulate JS API.
662     void loadImages(void delegate() onSuccess, void delegate() onFailure)
663     {
664         int counter = 0;
665         auto onSuccessInternal = delegate(IImage _)
666         {
667             if(++counter == tilesets.length)
668                 onSuccess();
669         };
670         foreach(HipTilesetImpl tileset; tilesets)
671             tileset.loadImage(onSuccessInternal, onFailure);
672     }
673 
674     bool loadTextures()
675     {
676         foreach(HipTilesetImpl tileset; tilesets)
677             if(!tileset.loadTexture())
678                 return false;
679         return true;
680     }
681     
682     override void onFinishLoading(){}
683     override void onDispose(){}
684     bool isReady(){return true;}
685     
686     
687 }